// 版权所有2011 Go作者。版权所有。
// 此源代码的使用受BSD样式
// 许可证的约束，该许可证可以在许可证文件中找到。

// 帮助函数使构造模板更容易。

package template

import (
	"fmt"
	"io/fs"
	"os"
	"path"
	"path/filepath"
)

// 解析模板的函数和方法。

// 必须是一个帮助程序，它包装对返回（*模板，错误）
// 的函数的调用，如果错误为非零，则会导致恐慌。它用于变量
// 初始化，如
// var t=template.Must（template.New（“name”）.Parse（“text”））
func Must(t *Template, err error) *Template {
	if err != nil {
		panic(err)
	}
	return t
}

// ParseFiles创建一个新模板，并从命名文件中解析模板定义。返回的模板名称将具有第一个文件的基本名称和
// 已解析内容。必须至少有一个文件。
// 如果发生错误，解析将停止，返回的*模板为零。
// 
// 当解析不同目录中具有相同名称的多个文件时，
// 最后一个提到的将是结果。
// 例如，ParseFiles（“a/foo”，“b/foo”）将“b/foo”存储为模板
// 命名为“foo”，而“a/foo”不可用。
func ParseFiles(filenames ...string) (*Template, error) {
	return parseFiles(nil, readFileOS, filenames...)
}

// ParseFiles解析命名文件，并将生成的模板与
// t关联。如果发生错误，解析停止，返回的模板为nil；
// 否则为t。必须至少有一个文件。
// 由于ParseFiles创建的模板是以参数文件的基本
// 名称命名的，所以t通常应该具有文件（基本）名称中的一个
// 的名称。否则，根据调用ParseFiles之前t的
// 内容，t.Execute可能会失败。在这种情况下，使用t.ExecuteTemplate执行有效的模板。
// 
// 当解析不同目录中具有相同名称的多个文件时，
// 最后一个提到的将是结果。
func (t *Template) ParseFiles(filenames ...string) (*Template, error) {
	t.init()
	return parseFiles(t, readFileOS, filenames...)
}

// parseFiles是方法和函数的助手。如果参数
// template为nil，则它是从第一个文件创建的。
func parseFiles(t *Template, readFile func(string) (string, []byte, error), filenames ...string) (*Template, error) {
	if len(filenames) == 0 {
		// 这不是问题，但要始终如一。
		return nil, fmt.Errorf("template: no files named in call to ParseFiles")
	}
	for _, filename := range filenames {
		name, b, err := readFile(filename)
		if err != nil {
			return nil, err
		}
		s := string(b)
		// 如果尚未定义，则第一个模板将成为返回值，
		// 我们在后续新调用中使用该模板将所有模板关联在一起。另外，如果此文件与t同名，则此文件将成为t的内容，因此
		// t，err:=New（name）.Funcs（xxx）.ParseFiles（name）
		// 。否则，我们将创建一个与t关联的新模板。
		var tmpl *Template
		if t == nil {
			t = New(name)
		}
		if name == t.Name() {
			tmpl = t
		} else {
			tmpl = t.New(name)
		}
		_, err = tmpl.Parse(s)
		if err != nil {
			return nil, err
		}
	}
	return t, nil
}

// ParseGlob创建一个新模板，并从模式标识的文件中解析模板定义。根据filepath.Match的
// 语义匹配文件，模式必须至少匹配一个文件。
// 返回的模板将具有模式匹配的第一个文件的（基本）名称和（已解析）内容。ParseGlob相当于调用
// ParseFiles，其中包含与模式匹配的文件列表。
// 
// 当解析不同目录中具有相同名称的多个文件时，
// 最后一个提到的将是结果。
func ParseGlob(pattern string) (*Template, error) {
	return parseGlob(nil, pattern)
}

// ParseGlob解析
// 模式标识的文件中的模板定义，并将生成的模板与t关联。根据filepath.Match的语义，文件是匹配的
// 且模式必须在
// 至少匹配一个文件。ParseGlob相当于使用
// 模式匹配的文件列表调用t.ParseFiles。
// 
// 当解析不同目录中具有相同名称的多个文件时，
// 最后一个提到的将是结果。
func (t *Template) ParseGlob(pattern string) (*Template, error) {
	t.init()
	return parseGlob(t, pattern)
}

// parseGlob是parseGlob函数和方法的实现。
func parseGlob(t *Template, pattern string) (*Template, error) {
	filenames, err := filepath.Glob(pattern)
	if err != nil {
		return nil, err
	}
	if len(filenames) == 0 {
		return nil, fmt.Errorf("template: pattern matches no files: %#q", pattern)
	}
	return parseFiles(t, readFileOS, filenames...)
}

// ParseFS类似于ParseFiles或ParseGlob，但从文件系统fsys 
// 而不是主机操作系统的文件系统读取。
// 它接受全局模式列表。
// （请注意，大多数文件名都是只匹配自身的全局模式。）
func ParseFS(fsys fs.FS, patterns ...string) (*Template, error) {
	return parseFS(nil, fsys, patterns)
}

// ParseFS类似于ParseFiles或ParseGlob，但从文件系统fsys 
// 而不是主机操作系统的文件系统读取。
// 它接受全局模式列表。
// （请注意，大多数文件名用作仅匹配自身的全局模式。）
func (t *Template) ParseFS(fsys fs.FS, patterns ...string) (*Template, error) {
	t.init()
	return parseFS(t, fsys, patterns)
}

func parseFS(t *Template, fsys fs.FS, patterns []string) (*Template, error) {
	var filenames []string
	for _, pattern := range patterns {
		list, err := fs.Glob(fsys, pattern)
		if err != nil {
			return nil, err
		}
		if len(list) == 0 {
			return nil, fmt.Errorf("template: pattern matches no files: %#q", pattern)
		}
		filenames = append(filenames, list...)
	}
	return parseFiles(t, readFileFS(fsys), filenames...)
}

func readFileOS(file string) (name string, b []byte, err error) {
	name = filepath.Base(file)
	b, err = os.ReadFile(file)
	return
}

func readFileFS(fsys fs.FS) func(string) (string, []byte, error) {
	return func(file string) (name string, b []byte, err error) {
		name = path.Base(file)
		b, err = fs.ReadFile(fsys, file)
		return
	}
}
